package model;

import java.util.Collection;
import java.util.Map;
import java.util.Set;
import java.util.Stack;
import java.util.TreeMap;
import java.util.List;
import java.util.ArrayList;
import java.util.TreeSet;
import java.util.Observable;
import java.util.Observer;
import java.awt.Graphics2D;
import java.io.Serializable;
import controller.TurtleController;
import parsing.Parser;
import util.coordTranslate.AbstractCoordinateSystem;
import view.panels.HistoryList;
import exceptions.InfiniteRecursionException;
import expressions.Expression;
import expressions.slogo.ITurtleContext;

/**
 * Stores the state of the program, including turtle locations, variable values,
 * and stuff like that.
 * 
 * @author Misha Lavrov, Alex Lee
 */
public class TurtleModel extends Observable implements ITurtleContext,
        Serializable, Observer
{
    /**
     * For serialization.
     */
    private static final long serialVersionUID = 1L;

    /*
     * All turtles refer to those turtles which have been instantiated. The spec
     * says that in the beginning, there are an infinite number of turtles all
     * in the origin. Obviously, we can't instantiate an infinite number of
     * turtles, so we'll instantiate them as they are referred to.
     * 
     * The Map is from Turtle ID (which the user will refer to) to the turtle.
     */
    private Map<Integer, Turtle> turtles;

    /*
     * Active turtles refer to those turtles who are actively listening to
     * commands; that is, commands only affect active turtles.
     */
    private List<Integer> activeTurtleIDs;

    /*
     * The turtle being currently evaluated.
     */
    private int currentTurtleID;

    /*
     * Variables are a map from their name to their value, an expression tree.
     */
    private Map<String, Expression<Double>> variables;

    /*
     * Values to be used in a recursive expression.
     */
    private Map<String, Stack<Double>> recursiveValues;

    /*
     * We keep track of variables in use so a variable doesn't refer to itself
     * in an infinite loop.
     */
    private Set<String> variablesInUse;

    /*
     * The controller and the parser.
     */
    private TurtleController myController;
    private Parser<Double> myParser;

    /**
     * Constructor. Sets the controller and creates the first turtle and sets it
     * to active.
     * 
     * @param controller
     */
    public TurtleModel(TurtleController controller)
    {
        super();
        addObserver(controller);

        /*
         * Link this model to the controller.
         */
        myController = controller;

        /*
         * Instantiate a parser to use.
         */
        myParser = new Parser<Double>("expressions.slogo");

        /*
         * Instantiate the first turtle.
         */
        Turtle firstTurtle = new Turtle();
        firstTurtle.addObserver(this);

        turtles = new TreeMap<Integer, Turtle>();
        turtles.put(0, firstTurtle);

        /*
         * By default, this turtle is active and listening to commands.
         */
        activeTurtleIDs = new ArrayList<Integer>();
        activeTurtleIDs.add(0);

        /*
         * Instantiate the variable map.
         */
        variables = new TreeMap<String, Expression<Double>>();
        variablesInUse = new TreeSet<String>();
        recursiveValues = new TreeMap<String, Stack<Double>>();
    }

    public Expression<Double> parseInput(String s)
    {
        return myParser.parseExpression(s);
    }

    /**
     * Gets the value of the variable with the given name. If the variable is
     * not in the variable map, return 0.
     * 
     * @param varName
     *            the name of the variable
     * @return the value of the variable
     */
    @Override
    public Double getValue(String varName)
    {
        if (recursiveValues.containsKey(varName))
            return recursiveValues.get(varName).peek();
        else if (variables.containsKey(varName))
            return evaluateSafely(varName);
        else
            return 0D;
    }

    private Double evaluateSafely(String varName)
    {
        if (variablesInUse.contains(varName))
            throw new InfiniteRecursionException(varName);

        variablesInUse.add(varName);

        try
        {
            return variables.get(varName).evaluate(this);
        }
        finally
        {
            /*
             * Make sure the variablesInUse are cleared at the end even if an
             * error happens, so that a variable doesn't get permanently marked
             * "in use".
             */
            variablesInUse.remove(varName);
        }
    }

    /**
     * Puts the given name/expression pair into the variable map
     */
    @Override
    public void setVariable(String varName, Expression<Double> variable)
    {
        variables.put(varName, variable);
    }

    public void setHistory(HistoryList list)
    {
    }

    public TurtleController getController()
    {
        return myController;
    }

    /**
     * Gets the list of turtles that have been instantiated.
     */
    public Collection<Turtle> getTurtles()
    {
        return turtles.values();
    }

    /**
     * Tells turtles to paint their icons.
     */
    public void tellTurtlesToPaintThemselves(Graphics2D graphics, AbstractCoordinateSystem coordinates)
    {
        for (Turtle t : turtles.values())
        {
            t.draw(graphics, coordinates);
        }
    }

    @Override
    public Turtle getTurtle()
    {
        if (turtles.containsKey(currentTurtleID))
        {
            return turtles.get(currentTurtleID);
        }
        else
        {
            Turtle t = new Turtle();
            turtles.put(currentTurtleID, t);
            t.addObserver(this);
            return t;
        }
    }

    @Override
    public Double evaluateForActiveTurtles(Expression<Double> expression)
    {
        List<Integer> turtleIDs = new ArrayList<Integer>(activeTurtleIDs);

        return evaluateForTurtles(expression, turtleIDs);

    }

    @Override
    public Double evaluateForTurtles(Expression<Double> expression, List<Integer> turtleIDs)
    {
        Double result = 0D;

        for (int id : turtleIDs)
        {
            currentTurtleID = id;
            result = expression.evaluate(this);
        }
        return result;
    }

    @Override
    public void setActiveTurtles(List<Integer> turtleIDs)
    {
        activeTurtleIDs.clear();
        activeTurtleIDs.addAll(turtleIDs);

        for (int id : turtleIDs)
        {
            if (!turtles.containsKey(id))
            {
                Turtle t = new Turtle();
                turtles.put(id, t);
                t.addObserver(this);
            }
        }
    }

    public List<Integer> getActiveTurtles()
    {
        return activeTurtleIDs;
    }

    public Turtle getTurtle(Integer id)
    {
        return turtles.get(id);
    }

    /**
     * Used as a part of the "clearscreen" command. Sets only Turtle 0 to be the
     * active turtle, and gets rid of all other turtles.
     */
    public void resetTurtles()
    {
        /*
         * Make a new list with just 0, and make that the list of active
         * turtles.
         */
        List<Integer> newlist = new ArrayList<Integer>();
        newlist.add(0);
        setActiveTurtles(newlist);

        /*
         * Get rid of all other turtles.
         */
        Turtle t = turtles.get(0);
        turtles.clear();
        turtles.put(0, t);
    }

    /**
     * Part of the Observer-Observable paradigm. Here, acknowledges that the
     * things that the model is observing (namely, the turtles) have changed,
     * and forwards that information to the Controller.
     */
    public void update(Observable thing, Object o)
    {
        /*
         * The state has changed, so notify the controller as such.
         */
        setChanged();
        notifyObservers(o);
    }

    @Override
    public void setValue(String varName, Double value)
    {
        if (!recursiveValues.containsKey(varName))
        {
            recursiveValues.put(varName, new Stack<Double>());
        }

        recursiveValues.get(varName).push(value);
    }

    @Override
    public void clearValue(String varName)
    {
        if (!recursiveValues.containsKey(varName))
            return;

        Stack<Double> values = recursiveValues.get(varName);

        values.pop();

        if (values.isEmpty())
            recursiveValues.remove(varName);
    }

}
